﻿namespace Zaggit.UI
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using Zaggit.Core;
    using Zaggit.DomainObjects;

    /// <summary>
    /// Displays the currently online users, offers IM abilities and system settings.
    /// </summary>
    public partial class Messages : Form
    {

        #region Fields

        /// <summary>
        /// Contains the thread which checks to see which users are currently online.
        /// </summary>
        private readonly System.Threading.Timer _CheckOnline;

        /// <summary>
        /// The currently selected user from the list of online users.
        /// </summary>
        private User _SelectedUser;

        private ZaggitSettings _SettingsWindow;
        /// <summary>
        /// The message handlers for different message types.
        /// </summary>
        private readonly IDictionary<MessageType, Action<UserMessage>> MessageHandlers;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Messages" /> class.
        /// </summary>
        /// <param name="messager">The messager system to use internally.</param>
        public Messages(ref IMessager messager)
        {
            InitializeComponent();
            this.MessageHandlers = new Dictionary<MessageType, Action<UserMessage>>();
            this.SetUpMessageHandlers();
            this.Messager = messager;
            this.Messager.MessageRecieved += MessageRecieved;
            userBindingSource.DataSource = new OnlineUsers();
            _CheckOnline = new System.Threading.Timer(x => UpdateOnlineList(), null, 500, 1000);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the messager.
        /// </summary>
        /// <value>
        /// The messager.
        /// </value>
        private IMessager Messager { get; set; }

        #endregion

        #region Private Methods

        /// <summary>
        /// Handles the Click event of the aboutToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Process.Start("http://zaggit.codeplex.com");
        }

        /// <summary>
        /// Handles the Tick event of the CheckIfTyping control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void CheckIfTyping_Tick(object sender, EventArgs e)
        {
            WhoIsTyping.Text = string.Empty;
            Action checkTyping = () =>
            {
                if (this.Message.Text.Length == 0) return;
                var msg = new UserMessage { Sender = Environment.UserName, Type = MessageType.Typing };
                Messager.Send(msg);

            };

            this.Invoke(checkTyping);
        }

        /// <summary>
        /// Handles the Click event of the clearToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void clearToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Conversation.Text = string.Empty;
        }

        /// <summary>
        /// Handles the LinkClicked event of the Conversation control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="LinkClickedEventArgs" /> instance containing the event data.</param>
        private void Conversation_LinkClicked(object sender, LinkClickedEventArgs e)
        {
            Process.Start(e.LinkText);
        }

        /// <summary>
        /// Handles the keep alive.
        /// </summary>
        private void HandleKeepAlive()
        {
            Action<UserMessage> handleUser = (message) =>
            {
                var existingUserPosition = userBindingSource.Find(System.ComponentModel.DependencyPropertyDescriptor.FromName("Name", typeof(User), typeof(User)), message.Sender);
                if (existingUserPosition > -1)
                {
                    var user = (User)userBindingSource[existingUserPosition];
                    user.LastSeen = DateTime.Now;
                    userBindingSource[existingUserPosition] = user;
                }
                else
                {
                    userBindingSource.Add(new User { UserName = message.Sender, LastSeen = DateTime.Now });
                }

            };

            this.MessageHandlers.Add(MessageType.KeepAlive, handleUser);
        }

        /// <summary>
        /// Handles the message.
        /// </summary>
        private void HandleMessage()
        {
            Action<UserMessage> appendMessage = (message) =>
            {
                this.Conversation.Text = this.Conversation.Text.Insert(0, string.Format("{0}: {1}{2}", message.Sender, message.Contents, Environment.NewLine));
            };

            this.MessageHandlers.Add(MessageType.Text, appendMessage);
            this.MessageHandlers.Add(MessageType.Private, appendMessage);

        }

        private void HandleWhoIsTyping()
        {
            Action<UserMessage> announceTyping = (message) =>
            {
                if (message.Sender != Environment.UserName)
                {
                    this.WhoIsTyping.Text = string.Format("{0} is typing...", message.Sender);
                }
            };

            this.MessageHandlers.Add(MessageType.Typing, announceTyping);
        }

        /// <summary>
        /// Inserts the private message to selected user.
        /// </summary>
        private void InsertPrivateMessageToSelectedUser()
        {
            Action insertPm = () =>
                            {
                                this.Message.Text = string.Format("@{0} {1}", this._SelectedUser.UserName, this.Message.Text);
                            };

            this.Invoke(insertPm);
        }

        /// <summary>
        /// Handles a received message.
        /// </summary>
        /// <param name="message">The message.</param>
        private void MessageRecieved(UserMessage message)
        {
            var handler = this.MessageHandlers[message.Type];

            Action handleMessage = () =>
                {
                    handler(message);
                };

            this.Invoke(handleMessage);
        }

        /// <summary>
        /// Handles the Activated event of the Messages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void Messages_Activated(object sender, EventArgs e)
        {
            Message.Focus();
        }

        /// <summary>
        /// Handles the FormClosing event of the Messages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="FormClosingEventArgs" /> instance containing the event data.</param>
        private void Messages_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (e.CloseReason == CloseReason.UserClosing)
            {
                e.Cancel = true;
                this.Hide();
                return;
            }

            _CheckOnline.Dispose();
        }

        /// <summary>
        /// Handles the Load event of the Messages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void Messages_Load(object sender, EventArgs e)
        {
            Message.Focus();
        }

        /// <summary>
        /// Handles the VisibleChanged event of the Messages control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void Messages_VisibleChanged(object sender, EventArgs e)
        {
            Message.Focus();
        }

        /// <summary>
        /// Handles the Click event of the quitToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void quitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }

        /// <summary>
        /// Handles the Click event of the saveToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveConversationDialog.Filter = "Rich text file (*.rtf)|*.rtf";
            SaveConversationDialog.Title = "Save Conversation";
            var result = SaveConversationDialog.ShowDialog();
            if (result.ToString() != "OK") return;

            var file = SaveConversationDialog.FileName;
            try
            {
                File.WriteAllText(file, Conversation.Text, new UTF32Encoding());
                MessageBox.Show("Conversation saved to " + file, "Conversation saved", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            catch (IOException)
            {
                MessageBox.Show("There was a problem saving the conversation.  Do you have access to the place you want to save to, or is the disk full?", "Conversation not saved", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Handles the Click event of the Send control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void Send_Click(object sender, EventArgs e)
        {
            var text = Message.Text;
            var recipient = string.Empty;
            if (text.StartsWith("@") && text.Contains(' '))
            {
                var endOfUsername = text.IndexOf(' ');
                recipient = text.Substring(0, endOfUsername);
            }
            var message = new UserMessage { Contents = text, Type = DomainObjects.MessageType.Text, Sender = Environment.UserName, Recipient = recipient };
            Messager.Send(message);
            Message.Text = string.Empty;
        }

        /// <summary>
        /// Handles the Click event of the sendPrivateMessageToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void sendPrivateMessageToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.InsertPrivateMessageToSelectedUser();
        }

        /// <summary>
        /// Handles the Click event of the settingsToolStripMenuItem control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (_SettingsWindow == null || _SettingsWindow.IsDisposed)
            {
                _SettingsWindow = new ZaggitSettings();
            }

            _SettingsWindow.Show();
            _SettingsWindow.Focus();
        }

        /// <summary>
        /// Sets up message handlers.
        /// </summary>
        private void SetUpMessageHandlers()
        {
            this.HandleKeepAlive();
            this.HandleWhoIsTyping();
            this.HandleMessage();
        }

        /// <summary>
        /// Updates the online list.
        /// </summary>
        private void UpdateOnlineList()
        {
            Action updateList = () =>
                {
                    var itemsToRemove = new List<User>();
                    foreach (User entry in userBindingSource)
                    {
                        if (entry.LastSeen.AddSeconds(4) < DateTime.Now) itemsToRemove.Add(entry);
                    }

                    itemsToRemove.ForEach(i => userBindingSource.Remove(i));
                };

            this.Invoke(updateList);
        }

        /// <summary>
        /// Handles the CellContentClick event of the WhosOnline control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="DataGridViewCellMouseEventArgs" /> instance containing the event data.</param>
        private void WhosOnline_CellContentClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            var item = userBindingSource[e.RowIndex];
            if (item == null) return;

            this._SelectedUser = (User)item;
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {
                OnlineContextMenu.Show(e.X, e.Y);
                return;
            }

            InsertPrivateMessageToSelectedUser();
        }

        #endregion

    }
}
